package com.piece.file.controller;

import com.piece.core.framework.constant.ApplicationConstants;
import com.piece.core.framework.constant.ExceptionConstants;
import com.piece.core.framework.constant.FrameWorkConstants;
import com.piece.core.framework.exception.ServerException;
import com.piece.core.framework.support.dto.UserDetailDTO;
import com.piece.core.framework.util.cache.CacheFactory;
import com.piece.core.framework.util.collection.CollectionUtil;
import com.piece.core.framework.util.net.HttpUtil;
import com.piece.core.log.config.UserContextHolder;
import com.piece.core.web.controller.FrameWorkController;
import com.piece.core.framework.support.convert.Convert;
import com.piece.core.framework.support.response.AjaxResponse;
import com.piece.core.framework.util.basic.I18nUtil;
import com.piece.core.framework.util.string.StringUtil;
import com.piece.core.framework.util.file.CompressUtil;
import com.piece.core.framework.util.file.FileUtil;
import com.piece.file.model.TbSysFile;
import com.piece.file.service.TbSysFileService;
import com.piece.file.client.FileClientFactory;
import com.piece.file.support.ChunkInfo;
import com.piece.file.support.ChunkResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Slf4j
@RestController
@Api(tags = {"公共文件管理"})
public class FrontFileController extends FrameWorkController {

    @Resource
    private TbSysFileService tbSysFileService;

    @Autowired
    private FileClientFactory fileClientFactory;

    @Resource
    private CacheFactory cacheFactory;

    private static String CHUNK_FILE_UPLOAD_PREFIX = "chunk_file_upload_";
    private static String CHUNK_FILE_UPLOAD_USER_PREFIX = "chunk_file_upload_user_";

    @ApiOperation(value = "通用文件上传")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "file_id", value = "原文件ID"),
            @ApiImplicitParam(name = "file", value = "文件表单"),
            @ApiImplicitParam(name = "path", value = "文件路径"),
            @ApiImplicitParam(name = "serverPath", value = "文件路径"),
            @ApiImplicitParam(name = "table", value = "业务表名"),
            @ApiImplicitParam(name = "businessId", value = "业务主键"),
            @ApiImplicitParam(name = "editor", value = "编辑器名称")
    })
    @RequestMapping(value = {"/api/file/upload", "/public/file/upload"})
    public AjaxResponse uploadFile(@RequestParam(value = "file", required = true) MultipartFile[] file, UserDetailDTO user) {
        try {
            String fileId = getRequest().getParameter("file_id");
            String path = Convert.toStr(getRequest().getParameter("path"), "");
            String serverPath = Convert.toStr(getRequest().getParameter("serverPath"));
            String table = getRequest().getParameter("table");
            String businessId = getRequest().getParameter("businessId");
            String fileType = Convert.toStr(getRequest().getParameter("fileType"));
            String editor = getRequest().getParameter("editor");
            if (StringUtil.isNotEmpty(fileType)) {
                businessId = fileType;
                table = "site_client_app";
                // 先删除原客户端程序
                tbSysFileService.deleteByTableAndBusiness(table, businessId);
            }

            if (null != file && file.length > 0) {
                TbSysFile tbSysFile;
                if (StringUtil.isNotEmpty(fileId)) {
                    tbSysFile = tbSysFileService.findById(fileId);
                    fileClientFactory.get().deleteFile(tbSysFile);
                }
                fileId = "";
                for (int i = 0; i < file.length; i++) {
                    if (StringUtil.isNotEmpty(serverPath)) {
                        byte[] bytes = FileUtil.toByte(file[i].getInputStream());
                        fileClientFactory.getNative().writeToLocalStore(bytes, serverPath, file[i].getOriginalFilename());
                    } else {
                        tbSysFile = fileClientFactory.get().store(file[i], path);
                        if (StringUtil.isNotEmpty(table)) {
                            tbSysFile.setTable(table);
                            tbSysFile.setBusinessId(businessId);
                        }
                        if (null != user) {
                            tbSysFile.setCreateBy(user.getId());
                        }
                        tbSysFileService.update(tbSysFile);
                        fileId += "," + tbSysFile.getFileId();
                    }
                }

                //返回下载地址
                String url = getRequestPrefix() + "/public/file/download";
                if (fileId.length() > 0) {
                    if ("editor-md".equals(editor)) {
                        url += "/" + fileId.substring(1);
                        AjaxResponse response = AjaxResponse.success(I18nUtil.message(ExceptionConstants.UPLOAD_SUCCESS), url);
                    }
                    return success(I18nUtil.message(ExceptionConstants.UPLOAD_SUCCESS), fileId.substring(1));
                } else if ("editor-md".equals(editor)) {
                    url += "?path=" + serverPath;
                    AjaxResponse response = AjaxResponse.success(I18nUtil.message(ExceptionConstants.UPLOAD_SUCCESS), url);
                } else {
                    return success(I18nUtil.message(ExceptionConstants.UPLOAD_SUCCESS));
                }
            }
        } catch (Exception e) {
            return error(I18nUtil.message(ExceptionConstants.UPLOAD_ERROR), e.getMessage());
        }
        return error(I18nUtil.message(ExceptionConstants.UPLOAD_ERROR));
    }

    @ApiOperation(value = "通用文件下载")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "delete", value = "是否删除源文件"),
            @ApiImplicitParam(name = "path", value = "文件路径")
    })
    @GetMapping(value = {"/api/file/download", "/public/file/download"})
    public void fileDownloadByPath(HttpServletRequest request, HttpServletResponse response) {
        try {
            String path = request.getParameter("path");
            path = URLDecoder.decode(path, "utf-8");
            boolean delete = Convert.toBool(request.getParameter("delete"), false);
            path = StringUtil.removeStart(path, FrameWorkConstants.UPLOAD_RESOURCE);
            File file = new File(path);
            String name = StringUtil.extractMessage(path, '[', ']');
            String pathName;
            if (name.equals(path)) {
                if (name.indexOf(File.separator) != -1) {
                    name = name.substring(name.lastIndexOf(File.separator) + 1);
                }
                pathName = name;
            } else {
                pathName = name + "." + FileUtil.getFileSuffix(path);
            }
            FileUtil.downloadForWeb(request, response, file, pathName);
            if (delete) {
                FileUtil.deleteFile(file.getPath());
            }
        } catch (Exception e) {
            log.error("下载文件失败", e);
            throw new ServerException(e.getMessage());
        }
    }

    @ApiOperation(value = "通用文件下载")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "fileId", value = "文件ID", type = "path")
    })
    @RequestMapping(value = {"/api/file/download/{fileId}", "/public/file/download/{fileId}"})
    public void fileDownload(@PathVariable String fileId, HttpServletRequest request, HttpServletResponse response) {
        boolean delete = Convert.toBool(request.getParameter("delete"), false);
        TbSysFile file = tbSysFileService.findById(fileId);

        try {
            if (null != file) {
                InputStream is = fileClientFactory.get().downloadFile(file);
                FileUtil.downloadForWeb(request, response, is, file.getOriginalFilename());
            }
        } catch (Exception e) {
            log.error("下载文件失败", e);
            throw new ServerException(e.getMessage());
        } finally {
            if (delete && null != file) {
                try {
                    fileClientFactory.get().deleteFile(file);
                } catch (Exception e) {}
            }
        }
    }

    @ApiOperation(value = "通用文件下载")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "table"),
            @ApiImplicitParam(name = "businessId")
    })
    @RequestMapping(value = {"/api/file/multi/download", "/public/file/multi/download"})
    public void fileDownload(HttpServletRequest request, HttpServletResponse response) {
        try {
            String table = request.getParameter("table");
            String businessId = request.getParameter("businessId");
            List<TbSysFile> files = tbSysFileService.findByTableAndBusiness(table, businessId);
            if (files.size() == 1) {
                InputStream is = fileClientFactory.get().downloadFile(files.get(0));
                FileUtil.downloadForWeb(request, response, is, files.get(0).getOriginalFilename());
            } else if (files.size() > 1) {
                List<String> fileNames = new ArrayList<>();
                List<InputStream> inputStreams = new ArrayList<>();
                for (TbSysFile file : files) {
                    inputStreams.add(fileClientFactory.get().downloadFile(file));
                    fileNames.add(file.getOriginalFilename());
                }
                CompressUtil.zipFile(inputStreams, fileNames, response.getOutputStream());
            }
            FileUtil.afterDownload(response, null, response.getOutputStream());
        } catch (Exception e) {
            log.error("下载文件失败", e);
            throw new ServerException(e.getMessage());
        }
    }

    @ApiOperation(value = "通用文件渲染浏览器")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "key", value = "文件路径"),
            @ApiImplicitParam(name = "path", value = "文件路径")
    })
    @RequestMapping(value = {"/api/file/preview", "/public/file/preview"})
    public void localFilePreview(HttpServletRequest request, HttpServletResponse response) {
        InputStream is = null;
        try {
            String url = request.getParameter("url");
            String path = request.getParameter("path");
            String key = request.getParameter("key");
            String fileName = null;

            if (StringUtil.isNotEmpty(url)) {
                is = HttpUtil.get(url).execute().download();
                fileName = FileUtil.getFileNameFromUrl(url);
            } else {
                path = null == path ? key : path;
                path = URLDecoder.decode(path, "utf-8");
                File file = new File(path);
                if (file.exists()) {
                    is = FileUtil.openInputStream(file);
                    fileName = file.getName();
                }
            }

            if (null != is) {
                if (FileUtil.checkImageType(fileName)) {
                    FileUtil.imageForWeb(request, response, is, fileName);
                } else if (FileUtil.checkAudioType(fileName)) {
                    FileUtil.audioForWeb(request, response, is, fileName, is.available());
                } else if (FileUtil.checkVideoType(fileName)) {
                    FileUtil.videoForWeb(request, response, is, fileName, is.available());
                } else {
                    FileUtil.objectForWeb(request, response, is, fileName);
                }
            } else {
                log.error("【" + path + "】文件不存在");
            }

        } catch (Exception e) {
            //log.error("渲染文件失败", e);
            //throw new ServerException(e.getMessage());
        }
    }

    @ApiOperation(value = "通用文件渲染浏览器")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "fileId", value = "文件ID", type = "path")
    })
    @RequestMapping(value = {"/api/file/preview/{fileId}", "/public/file/preview/{fileId}"})
    public void filePreview(@PathVariable String fileId, HttpServletRequest request, HttpServletResponse response) {
        try {
            TbSysFile file = tbSysFileService.findById(fileId);
            if (null != file) {
                InputStream is = fileClientFactory.get().downloadFile(file);
                if (FileUtil.checkImageType(file.getServerFileName())) {
                    FileUtil.imageForWeb(request, response, is, file.getOriginalFilename());
                } else if (FileUtil.checkAudioType(file.getServerFileName())) {
                    FileUtil.audioForWeb(request, response, is, file.getOriginalFilename(), file.getFileSize());
                } else if (FileUtil.checkVideoType(file.getServerFileName())) {
                    FileUtil.videoForWeb(request, response, is, file.getOriginalFilename(), file.getFileSize());
                } else {
                    FileUtil.objectForWeb(request, response, is, file.getOriginalFilename());
                }
            }
        } catch (Exception e) {
            //log.error("渲染文件失败", e);
            //throw new ServerException(e.getMessage());
        }
    }

    @ResponseBody
    @ApiOperation(value = "通用文件删除")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "key", value = "文件ID")
    })
    @RequestMapping(value = {"/api/file/delete", "/public/file/delete"})
    public AjaxResponse fileDelete() {
        try {
            String key = getRequest().getParameter("key");
            if (null != key) {
                TbSysFile file;
                String[] fileIds = key.split(FrameWorkConstants.SPLIT_PARAM);
                for (int i = 0; i < fileIds.length; i++) {
                    file = tbSysFileService.findById(key);
                    if (null != file) {
                        fileClientFactory.get().deleteFile(file);
                    }
                }
            }
            return success();
        } catch (Exception e) {
            log.error("删除文件失败", e);
            return error(e.getMessage());
        }
    }

    @ResponseBody
    @ApiOperation(value = "分片文件上传")
    @ApiImplicitParam(value = "分片文件数据", dataTypeClass = ChunkInfo.class, type = "body")
    @PostMapping(value = "/api/file/chunk/upload")
    public String chunkUpload(ChunkInfo chunk) {
        String code = "200";

        String root = applicationProperties.getOptions().get(ApplicationConstants.STORAGE_ROOT_PATH);
        String dir = Convert.toStr(getRequest().getParameter("path"), root);
        String folder = dir + File.separator + FileUtil.getFilePrefix(chunk.getFilename());
        String md5 = chunk.getIdentifier();

        MultipartFile file = chunk.getFile();
        try {
            byte[] bytes = file.getBytes();
            Path path = Paths.get(generateChunkPath(chunk, folder));
            // 文件写入指定路径
            Files.write(path, bytes);
            List<Integer> list = cacheFactory.get().getList(CHUNK_FILE_UPLOAD_PREFIX + md5);
            if (CollectionUtil.isEmpty(list)) {
                cacheFactory.get().pushList(CHUNK_FILE_UPLOAD_PREFIX + md5, Arrays.asList(chunk.getChunkNumber()));
            } else {
                cacheFactory.get().rightPush(CHUNK_FILE_UPLOAD_PREFIX + md5, chunk.getChunkNumber());
            }

            List<String> userChunks = cacheFactory.get().getList(CHUNK_FILE_UPLOAD_USER_PREFIX + UserContextHolder.getUserId());
            if (CollectionUtil.isEmpty(userChunks)) {
                cacheFactory.get().pushList(CHUNK_FILE_UPLOAD_USER_PREFIX + UserContextHolder.getUserId(), Arrays.asList(md5));
            } else {
                if (!userChunks.contains(md5)) {
                    cacheFactory.get().rightPush(CHUNK_FILE_UPLOAD_USER_PREFIX + UserContextHolder.getUserId(), md5);
                }
            }
        } catch (IOException e) {
            log.error("上传分片文件失败", e);
            FileUtil.deleteFile(folder + File.separator + chunk.getChunkNumber());
            code = "415";
        }

        return code;
    }

    @ResponseBody
    @ApiOperation(value = "分片文件校验")
    @ApiImplicitParam(value = "分片文件校验", dataTypeClass = ChunkInfo.class, type = "body")
    @GetMapping(value = "/api/file/chunk/upload")
    public ChunkResult chunkCheck(ChunkInfo chunk, HttpServletResponse response) {
        ChunkResult result = new ChunkResult();
        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);

        String root = applicationProperties.getOptions().get(ApplicationConstants.STORAGE_ROOT_PATH);
        String dir = Convert.toStr(getRequest().getParameter("path"), root);
        String md5 = chunk.getIdentifier();

        String file = dir + File.separator + chunk.getFilename();
        if (checkMd5(md5) || FileUtil.exists(file)) {
            result.setSkipUpload(true);
            result.setLocation(file);
            response.setStatus(HttpServletResponse.SC_OK);
            result.setMessage("文件秒传成功");
            return result;
        }

        List<Integer> list = cacheFactory.get().getList(CHUNK_FILE_UPLOAD_PREFIX + md5);
        if (CollectionUtil.isNotEmpty(list)) {
            result.setSkipUpload(false);
            result.setUploadedChunks(list);
            response.setStatus(HttpServletResponse.SC_OK);
            result.setMessage("部分文件块已存在，继续上传剩余文件块");
            return result;
        }

        return result;
    }

    @ResponseBody
    @ApiOperation(value = "分片文件合并")
    @ApiImplicitParam(value = "分片文件数据", dataTypeClass = ChunkInfo.class, type = "body")
    @PostMapping(value = "/api/file/chunk/merge")
    public AjaxResponse chunkMerge(@RequestBody ChunkInfo chunk) throws Exception {
        String root = applicationProperties.getOptions().get(ApplicationConstants.STORAGE_ROOT_PATH);
        String dir = Convert.toStr(getRequest().getParameter("path"), root);

        String file = dir + File.separator + chunk.getName();
        String folder = dir + File.separator + FileUtil.getFilePrefix(chunk.getName());
        String md5 = chunk.getUniqueIdentifier();

        if (checkMd5(md5) || FileUtil.exists(file)) {
            return success(I18nUtil.message(ExceptionConstants.UPLOAD_SUCCESS));
        }

        boolean merge = FileUtil.merge(file, folder, chunk.getName());
        if (merge) {
            boolean result1 = cacheFactory.get().delete(CHUNK_FILE_UPLOAD_PREFIX + md5);
            cacheFactory.get().removeList(CHUNK_FILE_UPLOAD_USER_PREFIX + UserContextHolder.getUserId(), 1L, md5);
            Files.delete(Paths.get(folder));
            return success(I18nUtil.message(ExceptionConstants.UPLOAD_SUCCESS));
        } else {
            Files.delete(Paths.get(file));
        }

        return error(I18nUtil.message(ExceptionConstants.UPLOAD_ERROR));
    }

    private boolean checkMd5(String md5) {
        List<TbSysFile> existFiles = tbSysFileService.findFileByMd5(md5);
        return existFiles.size() > 0;
    }

    private String generateChunkPath(ChunkInfo chunk, String filePath) {
        if (!Files.isWritable(Paths.get(filePath))) {
            try {
                Files.createDirectories(Paths.get(filePath));
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }

        return filePath + File.separator + chunk.getChunkNumber();
    }
}
